#include "stdafx.h"
#include "Renderer.h"
#include "U7Objs.h"
#include "MapChunk.h"
#include "WorldMap.h"
#include "U7FilesLoader.h"
#include "Shape.h"

#include <algorithm>

unsigned Renderer::renderSeq = 0;
std::map<SHAPEID,char*> Renderer::reflectMap;

inline bool operator < ( const SHAPEID& id1, const SHAPEID& id2 )
{
	return *(unsigned short*)&id1 < *(unsigned short*)&id2;
}

void Renderer::FreeResources()
{
	for ( std::map<SHAPEID,char*>::iterator itr = reflectMap.begin();
		itr != reflectMap.end();
		++itr )
	{
		delete itr->second;
	}
}

void Renderer::Paint( PGRAPHIC g, int ox, int oy, WorldMap& map, int zlimit )
{
	assert( ox >= 0 && oy >= 0 && ox < PIXELS_PER_MAP_IN_WIDTH && oy < PIXELS_PER_MAP_IN_HEIGHT );

	renderSeq++;

	graphic = g;
	files = map.u7Files;
	maxZLevel = zlimit;

	int startXChunk = ox / PIXELS_PER_CHUNK_IN_WIDTH;
	int startYChunk = oy / PIXELS_PER_CHUNK_IN_HEIGHT;
	int endXChunk = (ox + graphic->width  + PIXELS_PER_CHUNK_IN_WIDTH  - 1) / PIXELS_PER_CHUNK_IN_WIDTH;
	int endYChunk = (oy + graphic->height + 2 * PIXELS_PER_CHUNK_IN_HEIGHT - 1) / PIXELS_PER_CHUNK_IN_HEIGHT;

	// wrapped?
	endXChunk %= CHUNKS_PER_MAP_IN_WIDTH;
	endYChunk %= CHUNKS_PER_MAP_IN_HEIGHT;

	int oxChunk = -(ox % PIXELS_PER_CHUNK_IN_WIDTH);
	int oyChunk = -(oy % PIXELS_PER_CHUNK_IN_HEIGHT);

	{
		// paint terrain
		int ytmp = oyChunk;
		for ( int ychk = startYChunk; ychk != endYChunk; ychk = INCR_CHUNK(ychk) )
		{
			int xtmp = oxChunk;
			for ( int xchk = startXChunk; xchk != endXChunk; xchk = INCR_CHUNK(xchk) )
			{
				MapChunk* mapChunk = map.GetMapChunk( xchk, ychk );
				PaintTerrainTiles( xtmp, ytmp, mapChunk );
				xtmp += PIXELS_PER_CHUNK_IN_WIDTH;
			}
			ytmp += PIXELS_PER_CHUNK_IN_HEIGHT;
		}
	}

#if 1
	if ( maxZLevel >= -1 )
	{
		// paint flat terrain objects
		int ytmp = oyChunk;
		for ( int ychk = startYChunk; ychk != endYChunk; ychk = INCR_CHUNK(ychk) )
		{
			int xtmp = oxChunk;
			for ( int xchk = startXChunk; xchk != endXChunk; xchk = INCR_CHUNK(xchk) )
			{
				MapChunk* mapChunk = map.GetMapChunk( xchk, ychk );
				PaintTerrainFlats( xtmp, ytmp, mapChunk );
				xtmp += PIXELS_PER_CHUNK_IN_WIDTH;
			}
			ytmp += PIXELS_PER_CHUNK_IN_HEIGHT;
		}
	}
#endif

#if 1
	{
		//
		// paint objects
		//
		// the lord's room in the lord's castle was not built correctly in U7 BG
		//   the level of pillars is 1 not 0 which causes the algorithm can't paint objects correctly.
		// SOLUTION1:
		//   the objects whose levels are greater than 4 are always bigger than
		//   the objects whose levels are equal or below 4.
		//   this solution is not good enough, the algorithm of DimInfo.h is done in this way,
		//   but the visual is still bad.
		// SOLUTION2:
		//   in order that objects whose levels are greater than 4 don't interfece lower-level objects,
		//   paint object in two stages, lower then higher...


		bool done = false;
		int saveMaxZLevel =  maxZLevel;

		maxZLevel = min( maxZLevel, 4 );

		while ( !done )
		{
			int ytmp = oyChunk;
			for ( int ychk = startYChunk; ychk != endYChunk; ychk = INCR_CHUNK(ychk) )
			{
				int xtmp = oxChunk;
				for ( int xchk = startXChunk; xchk != endXChunk; xchk = INCR_CHUNK(xchk) )
				{
					MapChunk* mapChunk = map.GetMapChunk( xchk, ychk );
					PaintChunkObjects( xtmp, ytmp, mapChunk );
					xtmp += PIXELS_PER_CHUNK_IN_WIDTH;
				}
				ytmp += PIXELS_PER_CHUNK_IN_HEIGHT;
			}

			if ( saveMaxZLevel == maxZLevel )
				done = true; // break the loop
			else
				maxZLevel = saveMaxZLevel; // draw again for higher levels
		}
	}
#endif
}

void Renderer::PaintTerrainTiles( int ox, int oy, MapChunk* mapChunk )
{
	assert(mapChunk->patternID != 0xFFFF);
	if ( mapChunk->patternID == 0xFFFF )
		return;

	CHUNK_PATTERN chunkPattern = (CHUNK_PATTERN)files->chunkFile;
	SHAPEID (*pattern)[TILES_PER_CHUNK_IN_WIDTH] = chunkPattern[mapChunk->patternID];
			
	int yAnchor = oy + PIXELS_PER_TILE_IN_HEIGHT - 1;
	for ( int yt = 0; yt < TILES_PER_CHUNK_IN_HEIGHT; yt++ )
	{
		int xAnchor = ox + PIXELS_PER_TILE_IN_WIDTH - 1;
		for ( int xt = 0; xt < TILES_PER_CHUNK_IN_WIDTH; xt++ )
		{
			SHAPEID& shapeId = pattern[yt][xt];
			long shapeLength;
			PSHAPEENTRY shapeEntry = GetShape( files->shapeFile,
				shapeId.shapeType, &shapeLength );
			if ( IsShapeTile( shapeEntry, shapeLength ) )
			{
				DrawShape( graphic, shapeEntry, shapeLength, shapeId.shapeFrame, xAnchor, yAnchor );
			}
			xAnchor += PIXELS_PER_TILE_IN_WIDTH;
		}
		yAnchor += PIXELS_PER_TILE_IN_HEIGHT;
	}
}

void Renderer::PaintTerrainFlats( int ox, int oy, MapChunk* mapChunk )
{
	OBJLIST& list = mapChunk->objects;
	for ( OBJITR objItr = list.begin(); objItr != mapChunk->firstNonFlat; ++objItr )
	{
		BasicObject* obj = *objItr;
		obj->renderSeq = renderSeq;

		int liftPixels = 4 * obj->zlevel;

		int anchorX = ox + obj->xt * PIXELS_PER_TILE_IN_WIDTH  + PIXELS_PER_TILE_IN_WIDTH  - 1 - liftPixels;
		int anchorY = oy + obj->yt * PIXELS_PER_TILE_IN_HEIGHT + PIXELS_PER_TILE_IN_HEIGHT - 1 - liftPixels;

		DrawShape( graphic, files->shapeFile,
			obj->shapeID,
			anchorX,
			anchorY );
	}
}

void Renderer::PaintChunkObjects( int ox, int oy, MapChunk* mapChunk )
{
	OBJLIST& list = mapChunk->objects;
	for ( OBJITR objItr = mapChunk->firstNonFlat; objItr != list.end(); ++objItr )
	{
		if ( (*objItr)->renderSeq != renderSeq )
		{
			PaintObject(ox,oy,*objItr);
		}
	}
}

void Renderer::PaintObject( int ox, int oy, BasicObject* obj )
{
	// if this object is greater than maxZLevel,
	// ignore this and also don't check any dependencies.
	if ( obj->zlevel > maxZLevel )
		return;

	obj->renderSeq = renderSeq;

	for ( OBJITR depItr = obj->dependencies.begin(); depItr != obj->dependencies.end(); ++depItr )
	{
		if ( (*depItr)->renderSeq != renderSeq )
		{
			// can't handle wrapping
			int offx = ((*depItr)->mapChunk->xchunk - obj->mapChunk->xchunk);
			int offy = ((*depItr)->mapChunk->ychunk - obj->mapChunk->ychunk);
			assert( abs(offx) <= 1 && abs(offy) <= 1 );
			PaintObject(
				ox + offx * PIXELS_PER_CHUNK_IN_WIDTH,
				oy + offy * PIXELS_PER_CHUNK_IN_HEIGHT,
				*depItr );
		}
	}

	int liftPixels = 4 * obj->zlevel;

	int anchorX = ox + obj->xt * PIXELS_PER_TILE_IN_WIDTH  + PIXELS_PER_TILE_IN_WIDTH  - 1 - liftPixels;
	int anchorY = oy + obj->yt * PIXELS_PER_TILE_IN_HEIGHT + PIXELS_PER_TILE_IN_HEIGHT - 1 - liftPixels;


	const TFA* tfa = GetShapeTFA( files->tfaFile, obj->shapeID.shapeType );

	if ( tfa->containsTransparencyColors )
	{
		XFORMTBL xform = GetXformTables( files->xformTblFile );
		long shapeLength;
		PSHAPEENTRY shapeEntry = GetShape( files->shapeFile, obj->shapeID.shapeType, &shapeLength );
		PFRAMEENTRY frameEntry = GetShapeFrame( shapeEntry, shapeLength, obj->shapeID.shapeFrame );
		DrawShapeFrame( graphic, frameEntry,
			anchorX, anchorY, xform, 17 ); // hardcode 17
	}
	else if ( !obj->shapeID.xyflip )
	{
		DrawShape( graphic, files->shapeFile,
			obj->shapeID,
			anchorX,
			anchorY );
	}
	else
	{
		char* frameData = NULL;
		auto itr = reflectMap.find( obj->shapeID );
		if ( itr == reflectMap.end() )
		{
			//frameData = CreateReflectShapeFrame( *files, obj->shapeID );
			long shapeLength;
			PSHAPEENTRY shapeEntry = GetShape( files->shapeFile, obj->shapeID.shapeType, &shapeLength );
			PFRAMEENTRY frameEntry = GetShapeFrame( shapeEntry, shapeLength, obj->shapeID.shapeFrame );
			frameData = CreateReflectShapeFrame( frameEntry );
			reflectMap[obj->shapeID] = frameData;
		}
		else
		{
			frameData = itr->second;
		}

		DrawShapeFrame( graphic, (const FRAMEENTRY*)frameData, anchorX, anchorY );
	}
}

void Renderer::PaintTerrainTiles( PGRAPHIC g, const U7FilesLoader& files, int patternID )
{
	// patternID == 1843, something wrong,
	// frame index is greater than the frames of the tile set
	CHUNK_PATTERN chunkPattern = (CHUNK_PATTERN)files.chunkFile;
	SHAPEID (*pattern)[TILES_PER_CHUNK_IN_WIDTH] = chunkPattern[patternID];

	int yAnchor = PIXELS_PER_TILE_IN_HEIGHT - 1;
	for ( int yt = 0; yt < TILES_PER_CHUNK_IN_HEIGHT; yt++ )
	{
		int xAnchor = PIXELS_PER_TILE_IN_WIDTH - 1;
		for ( int xt = 0; xt < TILES_PER_CHUNK_IN_WIDTH; xt++ )
		{
			SHAPEID& shapeId = pattern[yt][xt];
			long shapeLength;
			PSHAPEENTRY shapeEntry = GetShape( files.shapeFile,
				shapeId.shapeType, &shapeLength );
			if ( IsShapeTile( shapeEntry, shapeLength ) )
			{
				DrawShape( g, shapeEntry, shapeLength, shapeId.shapeFrame, xAnchor, yAnchor );
			}
			xAnchor += PIXELS_PER_TILE_IN_WIDTH;
		}
		yAnchor += PIXELS_PER_TILE_IN_HEIGHT;
	}
}

